﻿using System;
using System.Collections.Generic;
using System.Linq;
using TimeMachine.Engine.Metrics;

namespace TimeMachine.Engine.Storing.Impl
{
	public class MetricStorage
	{
		private readonly IMetricsLoader loader;
		private readonly string metricId;
		private readonly IMetricsSaver saver;
		private List<MetricValue> values;
		private IDictionary<int, MetricValue> valuesByRev;

		public MetricStorage(IMetricsLoader loader, string metricId, IMetricsSaver saver)
		{
			this.loader = loader;
			this.saver = saver;
			this.metricId = metricId;
		}

		public IEnumerable<MetricValue> GetAll()
		{
			EnsureValuesLoaded();
			return values;
		}

		private void ReloadValues()
		{
			IEnumerable<MetricValue> metricValues = loader.Load(metricId);
			valuesByRev = new Dictionary<int, MetricValue>();
			values = new List<MetricValue>(metricValues);
			foreach (MetricValue metricValue in metricValues)
				valuesByRev.Add(metricValue.Rev, metricValue);
		}

		public MetricValue GetValue(int rev)
		{
			return
				TryGetValue(rev) ??
				InsteadOf<MetricValue>.Throw(
					new Exception(string.Format("Нет значения метрики {0} для ревизии {1}", metricId, rev)));
		}

		public MetricValue TryGetValue(int rev)
		{
			EnsureValuesLoaded();
			MetricValue result;
			if (!valuesByRev.TryGetValue(rev, out result)) return null;
			return result;
		}

		public MetricValue FindPrevValue(int rev)
		{
			EnsureValuesLoaded();
			if (!values.Any()) return null;
			if (rev > values.Last().Rev) return values.Last();
			int index = values.BinarySearch(v => v.Rev - rev);
			return index < 0 ? null : values[index];
		}

		private void EnsureValuesLoaded()
		{
			if (values == null) ReloadValues();
		}

		public void Take(MetricValue value)
		{
			EnsureValuesLoaded();
			valuesByRev[value.Rev] = value;
			int index = values.FindIndex(v => v.Rev >= value.Rev);
			if (index < 0) values.Add(value);
			else if (values[index].Rev == value.Rev) values[index] = value;
			else values.Insert(index, value);
			saver.WriteMetrics(metricId, values);
		}
	}
}